#ifndef NIGHT_LAMBDA_OPS
#define NIGHT_LAMBDA_OPS

#include <utility>
#include <functional>

#include "core.hpp"

namespace night { namespace lambda { 

struct Assign : lambda::Lambda<Assign>
{
	template<typename T1, typename T2>
	struct sig : std::binary_function<T1,T2,T1 const &> {};

	template<typename T1, typename T2>
	typename sig<T1,T2>::result_type operator () ( T1 & x, T2 & y ) const { return x = y; }
};
static Assign const assign = Assign ();

template<typename Expr> template<typename T>
detail::proxy< lambda::Assign, detail::tuple<Expr,T const> > Lambda<Expr>::operator = ( T const & val ) {
	return detail::proxy< lambda::Assign, detail::tuple<Expr,T const> > ( lambda::assign, detail::make_tuple ( this->deref (),val ) );
}

template<typename Expr> template<typename T>
detail::proxy< lambda::Assign, detail::tuple<Expr const,T const> > Lambda<Expr>::operator = ( T const & val ) const {
	return detail::proxy< lambda::Assign, detail::tuple<Expr const,T const> > ( lambda::assign, detail::make_tuple ( this->deref (),val ) );
}


#define MAKE_ASSIGN_OP(NAME,OP)                                             \
  struct NAME : lambda::Lambda<NAME>                                        \
  {                                                                         \
    template<typename T1, typename T2>                                      \
    struct sig : std::binary_function<T1,T2,T1 const &> {};                 \
                                                                            \
    template<typename T1, typename T2>                                      \
    typename sig<T1,T2>::result_type operator () ( T1 & x, T2 & y ) const { \
      return x OP y;                                                        \
    }                                                                       \
  };                                                                        \
  static NAME const do##NAME = NAME ();                                     \
                                                                            \
  namespace detail {                                                        \
                                                                            \
  template< typename T1, typename T2 >                                      \
  detail::proxy< lambda::NAME, detail::tuple<T1,T2 const> > operator OP ( T1 & x, T2 const & val ) {                         \
    return detail::proxy< lambda::NAME, detail::tuple<T1,T2 const> > ( lambda::do##NAME, detail::make_tuple (x,val) );       \
  }                                                                         \
                                                                            \
  template< typename T1, typename T2 >                                      \
  detail::proxy< lambda::NAME, detail::tuple<T1 const,T2 const> > operator OP ( T1 const & x, T2 const & val ) {             \
    return detail::proxy< lambda::NAME, detail::tuple<T1 const,T2 const> > ( lambda::do##NAME, detail::make_tuple (x,val) ); \
  }                                                                         \
                                                                            \
  }

MAKE_ASSIGN_OP (AddAssign,+=)
MAKE_ASSIGN_OP (SubAssign,-=)
MAKE_ASSIGN_OP (MulAssign,*=)
MAKE_ASSIGN_OP (DivAssign,/=)
MAKE_ASSIGN_OP (ModAssign,%=)

#undef MAKE_ASSIGN_OP

struct Deref : lambda::Lambda<Deref>
{
	template<typename T1>
	struct sig {
		typedef typename detail::cond<
			typename detail::is_iterator<T1>::reference,
			typename detail::is_pointer<T1>::reference
		>::type result_type;
	};

	template<typename T1>
	typename sig<T1>::result_type operator () ( T1 & x ) const { return (*x); }

};
static Deref const deref = Deref ();

namespace detail {

template<int i>
detail::proxy< lambda::Deref, detail::tuple< arg<i> const > > arg<i>::operator * ( ) const {
	return detail::proxy< lambda::Deref, detail::tuple< arg<i> const > > ( lambda::deref, detail::make_tuple (*this) );
}

} // end namespace night::lambda::detail

struct Pair : lambda::Lambda<Pair>
{
	template<typename First, typename Second>
	struct sig : std::binary_function < First, Second, std::pair<First,Second> > {};

	template<typename First, typename Second>
	typename sig<First,Second>::result_type operator () ( First & x, Second & y ) const {
		return std::make_pair (x,y);
	}

};
static Pair const pair = Pair ();

struct First : lambda::Lambda<First>
{
	template<class Pair>
	struct sig : std::unary_function < Pair, typename Pair::first_type & > {};

	template<class Pair>
	struct sig<Pair const> : std::unary_function < Pair, typename Pair::first_type > {};

	template<class Pair>
	typename sig<Pair>::result_type operator () ( Pair & p ) const { return p.first; }

};
static First const first = First ();

struct Second : lambda::Lambda<Second>
{
	template<class Pair>
	struct sig : std::unary_function < Pair, typename Pair::second_type & > {};

	template<class Pair>
	struct sig<Pair const> : std::unary_function < Pair, typename Pair::second_type > {};

	template<class Pair>
	typename sig<Pair>::result_type operator () ( Pair & p ) const { return p.second; }

};
static Second const second = Second ();

} } // end namespace night

namespace night { namespace lambda {

#define MAKE_UNARY_OP(NAME,name,OP,T1,RET)                                                    \
  struct NAME : lambda::Lambda<NAME>                                                          \
  {                                                                                           \
    template<typename T1>                                                                     \
    struct sig : std::unary_function <T1,RET> {};                                             \
                                                                                              \
    template<typename T1>                                                                     \
    typename sig<T1>::result_type operator () ( T1 & rhs ) const {                            \
      return OP (rhs);                                                                        \
    }                                                                                         \
  };                                                                                          \
  static NAME const name = NAME ();                                                           \
                                                                                              \
  namespace detail {                                                                          \
                                                                                              \
  template< typename T1 >                                                                     \
  detail::proxy< lambda::NAME, detail::tuple<T1 const> > operator OP ( T1 const & x ) {       \
    return detail::proxy< lambda::NAME, detail::tuple<T1 const> > ( lambda::name, detail::make_tuple (x) ); \
  }                                                                                           \
                                                                                              \
  }

MAKE_UNARY_OP (Negate,negate,-,T1,T1)
MAKE_UNARY_OP (Logical_not,logical_not,!,T1,bool)

#undef MAKE_UNARY_OP

} } // end namespace night

namespace night { namespace lambda {

#define MAKE_BINARY_OP(NAME,name,T1,OP,T2,RET)                                                \
  struct NAME : lambda::Lambda<NAME>                                                          \
  {                                                                                           \
    template<typename T1,typename T2>                                                         \
    struct sig : std::binary_function <T1,T2,RET> {};                                         \
                                                                                              \
    template<typename T1,typename T2>                                                         \
    typename sig<T1,T2>::result_type operator () ( T1 & lhs, T2 & rhs ) const {               \
      return (lhs) OP (rhs);                                                                  \
    }                                                                                         \
  };                                                                                          \
  static NAME const name = NAME ();                                                           \
                                                                                              \
  namespace detail {                                                                          \
                                                                                              \
  template< typename T1,typename T2 >                                                         \
  detail::proxy< lambda::NAME, detail::tuple<T1 const, T2 const> > operator OP ( T1 const & x, T2 const & y ) {         \
    return detail::proxy< lambda::NAME, detail::tuple<T1 const, T2 const> > ( lambda::name, detail::make_tuple (x,y) ); \
  }                                                                                           \
                                                                                              \
  }

MAKE_BINARY_OP (Logical_and,logical_and,T1,&&,T2,bool)
MAKE_BINARY_OP (Logical_or,logical_or,T1,||,T2,bool)

MAKE_BINARY_OP (Plus,plus,T1,+,T2,T1)
MAKE_BINARY_OP (Minus,minus,T1,-,T2,T1)
MAKE_BINARY_OP (Multiplies,mul,T1,*,T2,T1)
MAKE_BINARY_OP (Divides,div,T1,/,T2,T1)
MAKE_BINARY_OP (Modulus,mod,T1,%,T2,T1)

MAKE_BINARY_OP (Equal_to,equal,T1,==,T2,bool)
MAKE_BINARY_OP (Not_equal_to,not_equal,T1,!=,T2,bool)
MAKE_BINARY_OP (Greater,greater,T1,>,T2,bool)
MAKE_BINARY_OP (Less,less,T1,<,T2,bool)

#undef MAKE_BINARY_OP

} } // end namespace night

namespace night { namespace lambda { 

#define MAKE_TERNARY_OP(NAME,name,T1,OP1,T2,OP2,T3,RET)                                       \
  struct NAME : lambda::Lambda<NAME>                                                          \
  {                                                                                           \
    template<typename T1,typename T2=void,typename T3=void>                                   \
    struct sig { typedef RET result_type; };                                                  \
                                                                                              \
    template<typename T1>                                                                     \
    typename sig<T1>::result_type operator () ( T1 & x ) const { return x; }                  \
                                                                                              \
    template<typename T1,typename T2>                                                         \
    typename sig<T1,T2>::result_type operator () ( T1 & x, T2 & y ) const { return x OP1 y; } \
                                                                                              \
    template<typename T1,typename T2,typename T3>                                             \
    typename sig<T1,T2,T3>::result_type operator () ( T1 & x, T2 & y, T3 & z ) const {        \
      return x OP1 y OP2 z;                                                                   \
    }                                                                                         \
  };                                                                                          \
  static NAME const name = NAME ();

MAKE_TERNARY_OP (Summa,sum,T1,+,T2,+,T3,T1)
MAKE_TERNARY_OP (Product,prod,T1,*,T2,*,T3,T1)

#undef MAKE_TERNARY_OP

} } // end namespace night

#endif // NIGHT_LAMBDA_OPS
